// svn_access.cpp: implementation of the svn_access
//
//////////////////////////////////////////////////////////////////////

#include "svn_access.h"
#include <io.h>
#include <atlbase.h>
#include "tmcnv.h"

#include ".\tinyxml\tinyxml.h"

namespace process_helper
{

	bool invoke_svn(std::string svn, std::string param, std::string &output)
	{
// 		DWORD dwBufSize=MAX_PATH;
// 		TCHAR szTempName[MAX_PATH];
// 		TCHAR lpPathBuffer[MAX_PATH];
// 		// Get the temp path
// 		GetTempPath(dwBufSize,   // length of the buffer
// 			lpPathBuffer);      // buffer for path 
// 		
// // 		// Create a temporary file. 
// // 		GetTempFileName(lpPathBuffer, // directory for temp files 
// // 			"svnac",                    // temp file name prefix 
// // 			0,                        // create unique name 
// // 			szTempName);              // buffer for name 
// 		sprintf(szTempName, "%s%d.xml", lpPathBuffer, ::GetTickCount());
// 
// 		USES_CONVERSION;
// 		std::string output_file = T2CA(szTempName);
		char szCommand[MAX_PATH];
		sprintf(szCommand, "\"%s\" %s", svn.c_str(), param.c_str());

		output = "";
		HANDLE   input_read;
		HANDLE   input_write;
		HANDLE   output_read;
		HANDLE   output_write;
		
		STARTUPINFO si;               //Console Process creation information
		PROCESS_INFORMATION pi;
		SECURITY_ATTRIBUTES security = {sizeof(SECURITY_ATTRIBUTES), NULL,TRUE};
		CreatePipe(&input_read,&input_write,&security,0);
		CreatePipe(&output_read,&output_write,&security,0);
		
		ZeroMemory(&si,sizeof(STARTUPINFO));
		si.cb = sizeof(STARTUPINFO);
		si.lpReserved = NULL;
		si.lpReserved2 = NULL;
		si.cbReserved2 = 0;
		si.lpDesktop = NULL;
		si.wShowWindow = SW_HIDE;
		si.dwFlags = 0;
		si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
		si.hStdInput = input_read;
		si.hStdOutput = output_write;
		si.hStdError = output_write;
		si.wShowWindow = SW_HIDE;
		//char *env_current=NULL;
		//CreateEnvironmentBlock((LPVOID *)&env_current,NULL,TRUE);
		
		if (!CreateProcess( NULL,A2T(szCommand),&security,&security, TRUE, CREATE_NO_WINDOW|NORMAL_PRIORITY_CLASS
			, NULL, NULL, &si, &pi)) 
		{
			CloseHandle(input_read);
			CloseHandle(input_write);
			CloseHandle(output_read);
			CloseHandle(output_write);
			return false;
		}

		
		DWORD nStartTime = GetTickCount();
		while (1) 
		{ 
			DWORD w = WaitForSingleObject(pi.hProcess,1);
			
			char chRequest[4096];
			DWORD cbBytesRead,cbBytesAvail,cbBytesLeftMsg; 
			BOOL fSuccess; 
			fSuccess = PeekNamedPipe(output_read,chRequest,sizeof(chRequest),&cbBytesRead,&cbBytesAvail,&cbBytesLeftMsg);
			if (!fSuccess) break;
			
			if (cbBytesRead<=0) if (w==WAIT_TIMEOUT) 
			{
				//if (GetTickCount() - nStartTime > CGI_MAX_EXECUTE_TICK)
				//{
				////	TerminateProcess(pi.hProcess, 0);
				//	break;
				//}
				continue; 
			}
			else break;
			
			// Read client requests from the pipe. 
			memset(chRequest,0,sizeof(chRequest));
			fSuccess = ReadFile( 
				output_read,        // handle to pipe 
				chRequest,    // buffer to receive data 
				sizeof(chRequest)-1,      // size of buffer 
				&cbBytesRead, // number of bytes read 
				NULL);        // not overlapped I/O 
			if (! fSuccess || cbBytesRead == 0) 
				break; 
			if (cbBytesRead > 0)
			{
				chRequest[cbBytesRead] = 0;
				output += std::string(chRequest,cbBytesRead);
			}
		}
		
		CloseHandle(output_read);
		CloseHandle(output_write);
		if (!output.empty())
		{
// 			FILE *fp = fopen(output_file.c_str(), "wb");
// 			if (fp)
// 			{
// 				fwrite(output.c_str(), output.size(), 1, fp);
// 				fclose(fp);
// 			}
			return true;
		}
// 
// 
// 
// 		
// 		if (access(output_file.c_str(), 0) == -1) return false;
		return false;
	}
};


bool dir_scan_base::scan(std::string dir)
{
	WIN32_FIND_DATA FindFileData;
	HANDLE hFind = INVALID_HANDLE_VALUE;
	char DirSpec[MAX_PATH];  // directory specification
	DWORD dwError;
	
	sprintf(DirSpec, "%s\\*.*", dir.c_str());
	USES_CONVERSION;
	hFind = FindFirstFile(A2CT(DirSpec), &FindFileData);
	
	if (hFind == INVALID_HANDLE_VALUE) 
	{
		return false;
	} 
	else 
	{
		//printf ("First file name is %s\n", FindFileData.cFileName);
		on_entry_found(dir, FindFileData);
		while (FindNextFile(hFind, &FindFileData) != 0) 
		{
			on_entry_found(dir, FindFileData);
		}
		
		dwError = GetLastError();
		FindClose(hFind);
		return true;
	}
}

void dir_scan_base::on_entry_found(std::string dir, WIN32_FIND_DATA &FindFileData)
{
	if (_tcscmp(FindFileData.cFileName,_T(".")) == 0 || _tcscmp(FindFileData.cFileName,_T("..")) == 0)
		return;
	char DirSpec[MAX_PATH];  // directory specification
	USES_CONVERSION;
	sprintf(DirSpec, "%s\\%s", dir.c_str(), T2CA(FindFileData.cFileName));

	if ((FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY)
	{
		scan(DirSpec);
	}
	else
	{
		if (is_ext_in_list(find_file_ext(T2CA(FindFileData.cFileName)))) on_file_found(DirSpec);
	}
}

std::string dir_scan_base::find_file_ext(std::string filename)
{
	std::string::size_type pos = filename.rfind('.');
	if (pos != std::string::npos)
	{
		std::string ext = filename.substr(pos + 1, filename.size() - pos - 1);
		return ext;
	}
	return "";
}

bool dir_scan_base::is_ext_in_list(std::string ext)
{
	if (listExtentions.empty()) return true;

	std::list<std::string>::iterator i;
	for (i=listExtentions.begin();i!=listExtentions.end();i++)
	{
		if (stricmp((*i).c_str(), ext.c_str()) == 0) return true;
	}
	return false;
}

void svn_stat::on_file_found(std::string filename)
{
	printf("processing file: %s\n", filename.c_str());
	char param[MAX_PATH * 2];
	sprintf(param, "blame --xml -x -w \"%s\"", filename.c_str());
	std::string output;
	if (!process_helper::invoke_svn(svn_execute, param, output)) return;

	TiXmlDocument xmldoc;
	xmldoc.Parse(output.c_str());
	
	TiXmlElement *root = xmldoc.RootElement();
	if (!root) 
		return;
	std::string root_name = root->ValueStr();
	
	if (root_name != "blame") return;
	//entry here we go!
	TiXmlNode *child = root->FirstChild();
	if (!child) return;
	child = child->FirstChild();
	if (!child) return;

	while (child)
	{
		TiXmlElement *element = child->ToElement();
		if (strcmp(element->Value(),"entry")==0)
		{
			long revision;
			std::string author;
			std::string date;
			
			TiXmlNode *child2 = element->FirstChild();
			if (!child2) return;
			child2 = child2->FirstChild();
			while (child2)
			{
				TiXmlElement *element2 = child2->ToElement();
				if (strcmp(element2->Value(),"author")==0)
				{
					author = element2->GetText();
				}
				if (strcmp(element2->Value(),"date")==0)
				{
					date = element2->GetText();
				}
				child2 = child2->NextSibling();
			}
			//now we got an entry's whole information
			time_t lTime = 0;
			str2time(date.c_str(), &lTime);

			char sz[MAX_PATH];
			struct tm *tm = localtime(&(lTime));
			strftime(sz, MAX_PATH, "%Y-%m-%d", tm);
			
			std::map<long,std::list<AUTHOR_LINE> >::iterator i = mapDateAuthorLines.find(lTime);
			if (i != mapDateAuthorLines.end())
			{
				std::list<AUTHOR_LINE> &listAuthLines = (*i).second;
				std::list<AUTHOR_LINE>::iterator j;
				bool bFoundAuthor = false;
				for (j=listAuthLines.begin();j!=listAuthLines.end();j++)
				{
					if ((*j).author == author)
					{
						(*j).lines++;
						bFoundAuthor = true;
					}
				}
				if (!bFoundAuthor)
				{
					AUTHOR_LINE line;
					line.author = author;
					line.lines = 1;
					listAuthLines.push_back(line);
				}
			}
			else
			{
				std::list<AUTHOR_LINE> listAuthLines;
				AUTHOR_LINE line;
				line.author = author;
				line.lines = 1;
				listAuthLines.push_back(line);
				mapDateAuthorLines.insert(std::map<long,std::list<AUTHOR_LINE> >::value_type(lTime, listAuthLines));
			}
		}
		child = child->NextSibling();
	}
}


bool svn_stat::stat_dir_revisions()
{
	char param[MAX_PATH * 2];
	sprintf(param, "log --xml \"%s\"", stat_dir.c_str());
	std::string output;
	if (!process_helper::invoke_svn(svn_execute, param, output)) 
		return false;
	
	TiXmlDocument xmldoc;
	xmldoc.Parse(output.c_str());
	
	TiXmlElement *root = xmldoc.RootElement();
	if (!root) 
		return false;
	std::string root_name = root->ValueStr();

	if (root_name != "log") return false;

	//logentry here we go!
	TiXmlNode *child = root->FirstChild();
	while (child)
	{
		TiXmlElement *element = child->ToElement();
		if (strcmp(element->Value(),"logentry")==0)
		{
			long revision = atoi(element->Attribute("revision"));
			std::string author;
			std::string date;

			TiXmlNode *child2 = element->FirstChild();
			while (child2)
			{
				TiXmlElement *element2 = child2->ToElement();
				if (strcmp(element2->Value(),"author")==0)
				{
					author = element2->GetText();
				}
				if (strcmp(element2->Value(),"date")==0)
				{
					date = element2->GetText();
				}
				child2 = child2->NextSibling();
			}
			//now we got an entry's whole information
			time_t lTime = 0;
			str2time(date.c_str(), &lTime);

			mapRevisionDate.insert(std::map<long,long>::value_type(revision, lTime));
		}
		child = child->NextSibling();
	}
	return true;
}


std::string svn_stat::output()
{
	std::string output;
	std::map<long,std::list<AUTHOR_LINE> >::iterator i = mapDateAuthorLines.begin();
	char szLine[1024];
    for(; i!=mapDateAuthorLines.end(); i++)
	{
		long lTime = (*i).first;
		char sz[MAX_PATH];

		struct tm *tm = localtime(&(lTime));
		strftime(sz, MAX_PATH, "%Y-%m-%d", tm);
		
		sprintf(szLine, "%s\n", sz);
		output += szLine;
		std::list<AUTHOR_LINE> &listAuthLines = (*i).second;
		std::list<AUTHOR_LINE>::iterator j;
		bool bFoundAuthor = false;
		for (j=listAuthLines.begin();j!=listAuthLines.end();j++)
		{
			sprintf(szLine, "\tauthor: %s, lines: %d\n", (*j).author.c_str(), (*j).lines);
			output += szLine;
		}
	}
	return output;
}